package com.mebs.edi.parser.xml;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.apache.xerces.parsers.DOMParser;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

import com.mebs.edi.parser.utils.FileUtils;

/**
 * 
 * This class allows to parse XML (validating it against XSD schema)
 * and obtain corresponding document object
 * 
 * @author nit
 * @version 1.0
 * 
 */
public class XmlParser {

	private static class Validator extends DefaultHandler {

		public List<SAXParseException> saxParseExceptions = new ArrayList<SAXParseException>();

		public boolean validationError = false;

		@Override
		public void error(SAXParseException exception) throws SAXException {
			validationError = true;
			saxParseExceptions.add(exception);
		}

		@Override
		public void fatalError(SAXParseException exception) throws SAXException {
			validationError = true;
			saxParseExceptions.add(exception);
		}

		@Override
		public void warning(SAXParseException exception) throws SAXException {
		}
	}
	
	private static final Logger LOG = Logger.getLogger(XmlParser.class);
	
	private final File xsd;

	private final DOMParser domParser;

	/**
	 * 
	 * @param xsd .XSD schema file
	 * 
	 * @throws IllegalArgumentException if passed argument is not an XSD file
	 * @throws SAXException
	 * 
	 */
	public XmlParser(File xsd) throws IllegalArgumentException, SAXException {
		LOG.debug("Instantiating an XML Parser, schema: " + xsd.getAbsolutePath());
		
		if (!FileUtils.isValidFile(xsd, "xsd")) {
			throw new IllegalArgumentException("Not a valid .xsd file: " + xsd.getAbsolutePath());
		}
		this.xsd = xsd;
				
		domParser = new DOMParser();
		
		domParser.setFeature("http://xml.org/sax/features/validation", true);
		domParser.setFeature("http://apache.org/xml/features/validation/schema", true);
		domParser.setFeature("http://apache.org/xml/features/validation/schema-full-checking", true);

		domParser.setProperty("http://apache.org/xml/properties/schema/external-schemaLocation", 
				"http://www.w3.org/2001/XMLSchema " + xsd.getAbsolutePath());
		
		domParser.setErrorHandler(new Validator());
				
	}

	/**
	 * 
	 * Parses XML file, converting its contents to Document object (a set of DOM Node elements).
	 * Also validates the file against schema.
	 * 
	 * @param xml
	 * @return parsed document
	 * 
	 * @throws IllegalArgumentException if passed argument is not valid
	 * @throws IOException 
	 * @throws SAXException 
	 * 
	 */
	public Document parse(File xml) throws XmlValidationException, IllegalArgumentException, SAXException, IOException {
		LOG.debug("Validating " + xml.getAbsolutePath());

		if (!FileUtils.isValidFile(xml, "xml")) {
			throw new IllegalArgumentException("Not a valid .xml file");
		}
		
		domParser.parse(xml.getAbsolutePath());
		
		Validator handler = (Validator)domParser.getErrorHandler();
		if (handler.validationError == true) {
			LOG.info("XML Document has errors: " + handler.saxParseExceptions);
			throw new XmlValidationException("Validation against schema failed", xml, handler.saxParseExceptions);
		} else {      
			LOG.info("XML document is valid");
			return domParser.getDocument();
		}
	}
	
	public File getXsd() {
		return xsd;
	}
}
